require("medusa.js");

/************************************************************************/
/* Utils                                                                */
/************************************************************************/
medusa.swizzle = function (buf, len) {
    var t;
    for (var i = 0; i < len / 2; ++i) {
        t = buf[i];
        buf[i] = buf[len - i - 1];
        buf[len - i - 1] = t;
    }
}

/************************************************************************/
/* Buffer                                                               */
/************************************************************************/
medusa.Buffer = medusa.Class.extend({
    /*Clear, IsFull, IsEmpty,
    Size, ExpandSize, DataSize, FreeSize,
    LineDataSize, LineFreeSize,
    ReadInt8, WriteInt8*/

    /*
    this.Clear = Clear;
    this.IsFull = IsFull;
    this.IsEmpty = IsEmpty;
    this.Size = Size;
    this.ExpandSize = ExpandSize;
    this.DataSize = DataSize;
    this.FreeSize = FreeSize;
    this.LineDataSize = LineDataSize;
    this.LineFreeSize = LineFreeSize;
    this.ReadInt8 = ReadInt8;
    this.WriteInt8 = WriteInt8;*/

    /**
    * Constructor
    */
    ctor: function () {
    },

    init: function () {
    },
    // Boolean
    ReadBoolean: function () {
        return this.ReadInt8() != 0;
    },

    WriteBoolean: function (val) {
        this.WriteInt8((val ? 1 : 0));
    },

    // Int16
    ReadInt16LE: function () {
        var accum = 0;

        accum |= this.ReadInt8();
        accum |= this.ReadInt8() << 8;

        return accum;
    },

    ReadInt16BE: function () {
        var accum = 0;

        accum |= this.ReadInt8() << 8;
        accum |= this.ReadInt8();

        return accum;
    },

    WriteInt16LE: function (val) {
        this.WriteInt8((val) & 0xff);
        this.WriteInt8((val >> 8) & 0xff);
    },

    WriteInt16BE: function (val) {
        this.WriteInt8(val >> 8);
        this.WriteInt8(val);
    },

    // Int32
    ReadInt32LE: function () {
        var accum = 0;

        accum |= this.ReadInt8();
        accum |= this.ReadInt8() << 8;
        accum |= this.ReadInt8() << 16;
        accum |= this.ReadInt8() << 24;

        return accum;
    },

    WriteInt32LE: function (val) {
        this.WriteInt8((val) & 0xff);
        this.WriteInt8((val >> 8) & 0xff);
        this.WriteInt8((val >> 16) & 0xff);
        this.WriteInt8((val >> 24) & 0xff);
    },

    ReadInt32BE: function () {
        var accum = 0;

        accum |= this.ReadInt8() << 24;
        accum |= this.ReadInt8() << 16;
        accum |= this.ReadInt8() << 8;
        accum |= this.ReadInt8();

        return accum;
    },

    WriteInt32BE: function (val) {
        this.WriteInt8((val >> 24) & 0xff);
        this.WriteInt8((val >> 16) & 0xff);
        this.WriteInt8((val >> 8) & 0xff);
        this.WriteInt8((val) & 0xff);
    },

    // Int64
    ReadInt64LE: function () {
        var accum = 0;

        accum |= this.ReadInt8();
        accum |= this.ReadInt8() << 8;
        accum |= this.ReadInt8() << 16;
        accum |= this.ReadInt8() << 24;
        accum |= this.ReadInt8() << 32;
        accum |= this.ReadInt8() << 40;
        accum |= this.ReadInt8() << 48;
        accum |= this.ReadInt8() << 56;

        return accum;
    },

    WriteInt64LE: function (val) {
        this.WriteInt8((val) & 0xff);
        this.WriteInt8((val >> 8) & 0xff);
        this.WriteInt8((val >> 16) & 0xff);
        this.WriteInt8((val >> 24) & 0xff);
        this.WriteInt8((val >> 32) & 0xff);
        this.WriteInt8((val >> 40) & 0xff);
        this.WriteInt8((val >> 48) & 0xff);
        this.WriteInt8((val >> 56) & 0xff);
    },

    ReadInt64BE: function () {
        var accum = 0;

        accum |= this.ReadInt8() << 56;
        accum |= this.ReadInt8() << 48;
        accum |= this.ReadInt8() << 40;
        accum |= this.ReadInt8() << 32;
        accum |= this.ReadInt8() << 24;
        accum |= this.ReadInt8() << 16;
        accum |= this.ReadInt8() << 8;
        accum |= this.ReadInt8();

        return accum;
    },

    WriteInt64BE: function (val) {
        this.WriteInt8((val >> 56) & 0xff);
        this.WriteInt8((val >> 48) & 0xff);
        this.WriteInt8((val >> 40) & 0xff);
        this.WriteInt8((val >> 32) & 0xff);
        this.WriteInt8((val >> 24) & 0xff);
        this.WriteInt8((val >> 16) & 0xff);
        this.WriteInt8((val >> 8) & 0xff);
        this.WriteInt8((val) & 0xff);
    },

    // Float32
    ReadFloat32LE: function () {

        var buffer = new ArrayBuffer(4);
        var intView = new Int32Array(buffer);
        var floatView = new Float32Array(buffer);
        intView[0] = this.ReadInt32LE();

        return floatView[0];
    },

    WriteFloat32LE: function (val) {
        var buffer = new ArrayBuffer(4);
        var intView = new Int32Array(buffer);
        var floatView = new Float32Array(buffer);
        floatView[0] = val;

        this.WriteInt32LE(intView[0]);
    },

    ReadFloat32BE: function () {
        var buffer = new ArrayBuffer(4);
        var intView = new Int32Array(buffer);
        var floatView = new Float32Array(buffer);
        intView[0] = this.ReadInt32BE();

        return floatView[0];
    },

    WriteFloat32BE: function (val) {
        var buffer = new ArrayBuffer(4);
        var intView = new Int32Array(buffer);
        var floatView = new Float32Array(buffer);
        floatView[0] = val;

        this.WriteInt32BE(intView[0]);
    },

    // Float64
    ReadFloat64LE: function () {
        var buffer = new ArrayBuffer(8);
        var uint8View = new Uint8Array(buffer);
        var float64View = new Float64Array(buffer);

        //for (var i = 0; i < 8; i++ )
        //byteView[i] = this.ReadInt8();
        this.ReadToBlob(uint8View, 8);
        return float64View[0];
    },

    WriteFloat64LE: function (val) {
        var buffer = new ArrayBuffer(8);
        var uint8View = new Uint8Array(buffer);
        var float64View = new Float64Array(buffer);
        float64View[0] = val;

        //for (var i = 0; i < 8; i++)
        //this.WriteInt8(uint8View[0]);
        this.WriteBlob(uint8View, 8);
    },

    ReadFloat64BE: function () {
        var buffer = new ArrayBuffer(8);
        var uint8View = new Uint8Array(buffer);
        var float64View = new Float64Array(buffer);

        //for (var i = 0; i < 8; i++ )
        //byteView[i] = this.ReadInt8();
        this.ReadToBlob(uint8View, 8);
        medusa.swizzle(uint8View, 8);
        return float64View[0];
    },

    WriteFloat64BE: function (val) {
        var buffer = new ArrayBuffer(8);
        var uint8View = new Uint8Array(buffer);
        var float64View = new Float64Array(buffer);
        float64View[0] = val;

        medusa.swizzle(uint8View, 8);
        //for (var i = 0; i < 8; i++)
        //this.WriteInt8(byteView[0]);
        this.WriteBlob(uint8View, 8);
    },

    // Line
    ReadLine: function (lineendings) {
        //var line_size = 1024;
        var line = new Array();
        var line_index = 0;
        var linend = lineendings.split("");
        while (true) {
            // read
            line[line_index] = this.ReadInt8();
            if (line_index + 1 >= linend.length) {
                if (line.join("").indexOf(lineendings) >= 0)
                    break;
            }
            line_index++;
        }

        return line.join("");
    },

    WriteLine: function (val, lineendings) {

        var bytes = val.split("");
        var linend = lineendings.split("");
        this.WriteBlob(bytes, bytes.length);
        this.WriteBlob(linend, linend.length);
    },

    /*    
    Buffer.prototype.WriteLine = function(val) {
    var buf = val.split("");
    this.WriteBlob(buf, buf.length);
    this.WriteInt8('\n');
    }*/

    // String
    ReadString: function () {
        var size = this.ReadInt16BE();
        var str = "";
        if (size > 0) {
            var buf = this.ReadBlob(size);
            str = String.fromCharCode.apply(null, new Uint16Array(buf));
        }
        return str;
    },

    WriteString: function (val) {
        /*
        if (val != null) {
        var bytes = val.split("");
        var size = bytes.length;
        this.WriteInt16BE(size);
        this.WriteBlob(bytes, size);
        } else
        this.WriteInt16BE(0);
        */
        var size = val.length * 2;
        this.WriteInt16BE(size);
        var buf = new ArrayBuffer(val.length * 2); // 2 bytes for each char
        var int16View = new Uint16Array(buf);
        var int8View = new Uint8Array(buf);
        for (var i = 0; i < size; i++) {
            int16View[i] = val.charCodeAt(i);
        }
        this.WriteBlob(int8View, size);
    },

    //Blob
    ReadBlob: function (size) {
        /*
        //var bytes = new Uint8Array(size);
        var bytes = new Array();
        for (var i = 0; i < size; i++) {
        bytes[i] = this.ReadInt8();
        }*/
        /*
        var buf = this.ReadBlob(size);
        var str = String.fromCharCode.apply(null, new Uint16Array(buf));
        return str.split("");
        */
        var buf = new ArrayBuffer(size);
        var uint8View = new Uint8Array(buf);
        for (var i = 0; i < size; i++) {
            uint8View[i] = this.ReadInt8();
        }

        return buf;
    },

    ReadToBlob: function (uint8arr, size) {
        for (var i = 0; i < size; i++) {
            uint8arr[i] = this.ReadInt8();
        }
        return uint8arr;
    },

    WriteBlob: function (uint8arr, size) {
        for (var i = 0; i < size; i++) {
            this.WriteInt8(uint8arr[i]);
        }
    },

    ReadBuffer: function (dst, size) {
        for (var i = 0; i < size; i++) {
            dst.WriteInt8(this.ReadInt8());
        }
    },

    WriteBuffer: function (src, size) {
        for (var i = 0; i < size; i++) {
            this.WriteInt8(src.ReadInt8());
        }
    }
});

/************************************************************************/
/* LineBuffer                                                           */
/************************************************************************/
medusa.LineBuffer = medusa.Buffer.extend({

    // Data Member
    buffer: null,
    buffer_size: 0,
    expand_size: 0,
    read_offset: 0,
    write_offset: 0,

    /**
    * Constructor
    */
    ctor: function (buf_size, expand_size) {
        this._super();

        this.buffer = new Uint8Array(buf_size);
        this.buffer_size = buf_size;
        this.expand_size = expand_size;
        this.read_offset = 0;
        this.write_offset = 0;
    },

    /**
    * Initialize
    * @return {Boolean}
    */
    init: function () {
        this._super();
    },

    Clear: function () {
        this.read_offset = this.write_offset = 0;
    },

    IsFull: function () {
        return (this.write_offset == this.buffer_size);
    },

    IsEmpty: function () {
        return (this.write_offset == 0);
    },

    BufferSize: function () {
        return this.buffer_size;
    },

    ExpandSize: function () {
        return this.expand_size;
    },

    DataSize: this.LineDataSize = function () {
        return this.write_offset - this.read_offset;
    },

    FreeSize: this.LineFreeSize = function () {
        return this.buffer_size - this.write_offset;
    },

    // Int8
    ReadInt8: function () {
        var ret = 0;
        if (!this.IsEmpty()) {//this.read_offset < this.write_offset
            ret = this.buffer[this.read_offset];
            this.read_offset++;
        }

        return ret;
    },

    WriteInt8: function (val) {
        if (this.IsFull()) {//(this.write_offset + 1) > this.buffer_size
            var new_buff_size = this.buffer_size + this.expand_size;
            var new_buffer = new Uint8Array(new_buff_size);
            var old_buffer = this.buffer;
            var data_size = this.DataSize();

            for (var i = 0; i < data_size; i++)
                new_buffer[i] = old_buffer[this.read_offset + i];

            this.buffer = new_buffer;
            this.buffer_size = new_buff_size;
            this.read_offset = 0;
            this.write_offset = data_size;
        }

        this.buffer[this.write_offset] = val;
        this.write_offset++;
    }
});

/************************************************************************/
/* RingBuffer                                                           */
/************************************************************************/
medusa.RingBuffer = medusa.Buffer.extend({
    buffer: null,
    buffer_size: 0,
    expand_size: 0,
    read_offset: 0,
    write_offset: 0,

    /**
    * Constructor
    */
    ctor: function (buf_size, expand_size) {
        this._super();

        this.buffer = new Uint8Array(buf_size);
        this.buffer_size = buf_size;
        this.expand_size = expand_size;
        this.read_offset = 0;
        this.write_offset = 0;
    },

    /**
    * Initialize
    * @return {Boolean}
    */
    init: function () {
        this._super();

    },

    Clear: function () {
        this.read_offset = this.write_offset = 0;
    },

    IsFull: function () {
        return (((this.write_offset + 1) %
                 this.buffer_size) == this.read_offset);
    },

    IsEmpty: function () {
        return (this.read_offset == this.write_offset);
    },

    BufferSize: function () {
        return this.buffer_size;
    },

    ExpandSize: function () {
        return this.expand_size;
    },

    DataSize: function () {
        return (this.write_offset + this.buffer_size - this.read_offset) % this.buffer_size;
    },

    FreeSize: function () {
        return this.buffer_size - this.DataSize();
    },

    LineDataSize: function () {
        if (this.write_offset == this.read_offset)
            return 0;

        return (this.write_offset > this.read_offset) ?
        (this.write_offset - this.read_offset)
        : (this.buffer_size - this.read_offset);
    },

    LineFreeSize: function () {
        if (this.write_offset == this.read_offset)
            return this.buffer_size - this.write_offset;

        return (this.write_offset > this.read_offset) ?
        (this.buffer_size - this.write_offset)
        : (this.read_offset - this.write_offset);
    },

    // Int8
    ReadInt8: function () {
        var ret = 0;
        if (!this.IsEmpty()) {
            ret = this.buffer[this.read_offset];
            this.add_read_offset(1);
        }
        return ret;
    },

    WriteInt8: function (val) {
        if (this.IsFull()) {
            // expand:
            var new_buff_size = this.buffer_size + this.expand_size;
            var new_buffer = new Uint8Array(new_buff_size);
            //var old_buffer = this.buffer;
            //var data_size = this.DataSize();
            var size = 0;
            while (!this.IsEmpty()) {
                new_buffer[size] = this.ReadInt8();
                size++;
            }
            this.buffer = new_buffer;
            this.buffer_size = new_buff_size;
            this.read_offset = 0;
            this.write_offset = size;
        }
        this.buffer[this.write_offset] = val;
        this.add_write_offset(1);
    },

    // offset
    add_read_offset: function (offset) {
        this.read_offset = ((this.read_offset + offset) % this.buffer_size);
        return this.read_offset;
    },

    add_write_offset: function (offset) {
        this.write_offset = ((this.write_offset + offset) % this.buffer_size);
        return this.write_offset;
    }

});
